package hos.map.coordinate;

import java.util.List;
import java.util.Map;
import java.util.Objects;

import hos.map.distance.BearingDistanceCache;
import hos.map.distance.DistanceCalculator;
import hos.map.distance.DistanceCalculatorInterface;
import hos.map.distance.LengthUnit;
import hos.map.geometry.Geometry;
import hos.map.tool.CalculatorUtils;
import hos.map.tool.MathUtil;

/**
 * <p>Title: LatLng </p>
 * <p>Description: 经纬度 </p>
 * <p>Company: www.mapuni.com </p>
 *
 * @author : 蔡俊峰
 * @version : 1.0
 * @date : 2023-12-06 22:51
 */
public class LatLng extends Geometry implements DistanceCalculatorInterface {
    private final double latitude;
    private final double longitude;

    private DistanceCalculator mDistanceCalculator;

    /**
     * 从map中获取经纬度
     */
    public static LatLng fromMap(Map<String, Double> dataMap, CoordinateType type) {
        if (dataMap == null) {
            return null;
        }
        return new LatLng(dataMap.get("latitude"), dataMap.get("longitude"), type);
    }

    public static LatLng fromMapLatLng(Map<String, Object> dataMap) {
        return fromMapLatLng(dataMap, null);
    }

    /**
     * 从map中获取经纬度
     */
    public static LatLng fromMapLatLng(Map<String, Object> dataMap, CoordinateType type) {
        if (dataMap == null) {
            return null;
        }
        double longitude = (double) dataMap.get("longitude");
        double latitude = (double) dataMap.get("latitude");
        if (type == null) {
            String typeName = (String) dataMap.get("type");
            type = CoordinateType.form(typeName);
        }
        LatLng latLng = new LatLng(latitude, longitude, type);
        if (dataMap.containsKey("json")) {
            latLng.setJson((String) dataMap.get("json"));
        }
        if (dataMap.containsKey("id")) {
            latLng.id = (String) dataMap.get("id");
        }
        return latLng;
    }

    public LatLng(double latitude, double longitude, CoordinateType type) {
        super(type);
        this.latitude = latitude;
        this.longitude = longitude;
    }

    @SuppressWarnings("CopyConstructorMissesField")
    public LatLng(LatLng latLng) {
        super(latLng.getType());
        this.latitude = latLng.getLatitude();
        this.longitude = latLng.getLongitude();
    }

    public double getLatitudeInRad() {
        return Math.toRadians(latitude);
    }

    public double getLongitudeInRad() {
        return Math.toRadians(longitude);
    }

    public double getLatitude() {
        return latitude;
    }

    public double getLongitude() {
        return longitude;
    }

    public int getLatitudeInt() {
        return (int) latitude;
    }

    public int getLongitudeInt() {
        return (int) longitude;
    }

    public float getLatitudeFloat() {
        return (float) latitude;
    }

    public float getLongitudeFloat() {
        return (float) longitude;
    }

    public String getLongitude6() {
        return getLongitude(6);
    }

    public String getLatitude6() {
        return getLatitude(6);
    }

    public String getLongitude(int num) {
        return String.format("%." + num + "f", longitude);
    }

    public String getLatitude(int num) {
        return String.format("%." + num + "f", latitude);
    }

    /**
     * 19° 22' 32.00"
     *
     * @return 19° 22' 32.00"
     */
    public String getLatitudeSexagesimal() {
        return MathUtil.decimal2sexagesimal(latitude);
    }

    /**
     * 19° 22' 32.00"
     *
     * @return 19° 22' 32.00"
     */
    public String getLongitudeSexagesimal() {
        return MathUtil.decimal2sexagesimal(longitude);
    }


    /**
     * 创建一个新的[LatLng]，其中[经度]和[纬度]值四舍五入到最接近的整数。
     */
    public LatLng round() {
        return new LatLng(Math.round(latitude), Math.round(longitude), getType());
    }

    /**
     * 创建一个新的[LatLng]，其中[经度]和[纬度]值四舍五入到最接近的整数。
     */
    public LatLng ceil() {
        return new LatLng(Math.ceil(latitude), Math.ceil(longitude), getType());
    }

    /**
     * 创建一个新的[LatLng]，其中[经度]和[纬度]值被舍入到最接近的整数。
     */
    public LatLng floor() {
        return new LatLng(Math.floor(latitude), Math.floor(longitude), getType());
    }

    /**
     * 创建一个新的[LatLng]，其[longitude]和[latitude]值顺时针旋转
     */
    public LatLng rotate(double radians) {
        if (radians != 0.0) {
            final double cosTheta = Math.cos(radians);
            final double sinTheta = Math.sin(radians);
            final double nx = (cosTheta * latitude) + (sinTheta * longitude);
            final double ny = (cosTheta * longitude) - (sinTheta * latitude);

            return new LatLng(ny, nx, getType());
        }
        return this;
    }

    @Override
    public boolean equals(Object other) {
        if (other instanceof LatLng) {
            LatLng otherLatLng = (LatLng) other;
            return otherLatLng.latitude == latitude && otherLatLng.longitude == longitude;
        }
        return super.equals(other);
    }

    /**
     * 对比小数点后6位
     */
    public boolean equals6(Object other) {
        if (other instanceof LatLng) {
            LatLng otherLatLng = (LatLng) other;
            return Objects.equals(otherLatLng.getLatitude6(), getLatitude6()) &&
                    Objects.equals(otherLatLng.getLongitude6(), getLongitude6());
        }
        return super.equals(other);
    }

    /**
     * 51° 31' 10.11" N, 19° 22' 32.00" W 转换为经纬度
     */
    public static LatLng fromSexagesimal(final String latitude, final String longitude, CoordinateType type) {
        double _latitude = 0.0;
        double _longitude = 0.0;
        _latitude = MathUtil.sexagesimal2decimal(latitude);
        _longitude = MathUtil.sexagesimal2decimal(longitude);
        if (latitude.contains("S")) {
            _latitude = -_latitude;
        }
        if (longitude.contains("W")) {
            _longitude = -_longitude;
        }
        return new LatLng(_latitude, _longitude, type);
    }

    /**
     * 51° 31' 10.11" N, 19° 22' 32.00" W 转换为经纬度
     */
    public static LatLng fromSexagesimal(final String str, CoordinateType type) {
        double _latitude = 0.0;
        double _longitude = 0.0;
        // try format '''47° 09' 53.57" N, 8° 32' 09.04" E'''
        String[] splits = str.split(",");
        if (splits.length != 2) {
            // try format '''N 47°08'52.57" E 8°32'09.04"'''
            splits = str.split("E");
            if (splits.length != 2) {
                // try format '''N 47°08'52.57" W 8°32'09.04"'''
                splits = str.split("W");
                if (splits.length != 2) {
                    throw new StringIndexOutOfBoundsException("Unsupported sexagesimal format: " + str);
                }
            }
        }
        _latitude = MathUtil.sexagesimal2decimal(splits[0]);
        _longitude = MathUtil.sexagesimal2decimal(splits[1]);
        if (str.contains("S")) {
            _latitude = -_latitude;
        }
        if (str.contains("W")) {
            _longitude = -_longitude;
        }
        return new LatLng(_latitude, _longitude, type);
    }

    /**
     * 将经纬度转换为 51° 31' 10.11" N, 19° 22' 32.00" W
     */
    public String toSexagesimal() {
        String latDirection = latitude >= 0 ? "N" : "S";
        String lonDirection = longitude >= 0 ? "E" : "W";
        return MathUtil.decimal2sexagesimal(latitude) + " " + latDirection + ", "
                + MathUtil.decimal2sexagesimal(latitude) + " " + lonDirection;
    }

    @Override
    public String toString() {
        return "Lat: " + latitude + ", Lng: " + longitude + "\n" +
                "Lat6: " + getLatitude6() + ", Lng6: " + getLongitude6() +
                "LatSexagesimal: " + getLatitudeSexagesimal() + ", LngSexagesimal: " + getLongitudeSexagesimal() +
                "LatInRad: " + getLatitudeInRad() + ", LngInRad: " + getLongitudeInRad();
    }


    @Override
    public LatLng toBaiDu() {
        return CoordinateConverter.toBaiDu(this);
    }

    @Override
    public LatLng toAMap() {
        return CoordinateConverter.toAMap(this);
    }

    @Override
    public LatLng toWgs() {
        return CoordinateConverter.toWgs(this);
    }

    /**
     * 设置距离计算
     *
     * @param mDistanceCalculator 距离计算
     */
    public void setDistanceCalculator(DistanceCalculator mDistanceCalculator) {
        this.mDistanceCalculator = mDistanceCalculator;
    }

    /**
     * 计算两点之间的距离
     *
     * @param target 目标点位
     * @return 距离
     */
    @Override
    public BearingDistanceCache distance(LatLng target) {
        if (mDistanceCalculator != null) {
            return mDistanceCalculator.distance(this, target);
        }
        return CalculatorUtils.distanceVincenty(this, target);
    }

    @Override
    public LatLng offset(double distanceInMeter, double bearing) {
        if (mDistanceCalculator != null) {
            return mDistanceCalculator.offset(this, distanceInMeter, bearing);
        }
        return CalculatorUtils.offsetVincenty(this, distanceInMeter, bearing);
    }

    @Override
    public boolean isPolygonContains(List<LatLng> target) {
        if (mDistanceCalculator != null) {
            return mDistanceCalculator.isPolygonContains(target, this);
        }
        return CalculatorUtils.isPolygonContains(target, this);
    }

    @Override
    public boolean isCircleRepeated(LatLng latLng, int radius) {
        if (mDistanceCalculator != null) {
            return mDistanceCalculator.isPointInside(this, radius, latLng);
        }
        return CalculatorUtils.isPointInsideVincenty(this, radius, latLng);
    }

    /**
     * 判断经纬度是否相同
     *
     * @param latLng 目标经纬度
     */
    public boolean isRepeated(LatLng latLng) {
        if (latLng == null) {
            return false;
        }
        // 判断本次和上一次经纬度是否一样
        String longitude = getLongitude6();
        String latitude = getLatitude6();
        String lastLongitude = latLng.getLongitude6();
        String lastLatitude = latLng.getLatitude6();
        return Objects.equals(longitude, lastLongitude) && Objects.equals(latitude, lastLatitude);
    }

    /**
     * 计算两点之间的距离
     *
     * @param target 目标点位
     * @param unit   单位
     * @return 距离
     */
    @Override
    public BearingDistanceCache distance(LengthUnit unit, LatLng target) {
        if (mDistanceCalculator != null) {
            return mDistanceCalculator.distance(unit, this, target);
        }
        return CalculatorUtils.distanceVincenty(unit, this, target);
    }

    @Override
    public double bearing(LatLng target) {
        if (mDistanceCalculator != null) {
            return mDistanceCalculator.bearing(this, target);
        }
        return CalculatorUtils.bearing(this, target);
    }

    @Override
    public LatLng getCenter(LatLng latLng) {
        double lat = latLng.getLatitude() + latitude / 2;
        double lng = latLng.getLongitude() + longitude / 2;
        return new LatLng(lat, lng, getType());
    }

    @Override
    public LatLng getCenter(LatLngBounds latLng) {
        return latLng.extend(this).getCenter();
    }

    @Override
    public LatLng getCenter(LatLngCollection collection) {
        if (collection.size() == 0) {
            return this;
        }
        if (collection.size() == 1) {
            return this.getCenter(collection.get(0));
        }
        collection.add(this);
        return collection.getCenter();
    }

    @Override
    public Map<String, Object> toMap() {
        Map<String, Object> data = super.toMap();
        data.put("latitude", latitude);
        data.put("longitude", longitude);
        data.put("latitude6", getLatitude6());
        data.put("longitude6", getLongitude6());
        data.put("latitudeSexagesimal", getLatitudeSexagesimal());
        data.put("longitudeSexagesimal", getLongitudeSexagesimal());
        return data;
    }


    public String toJsonAll() {
        String gpsJson = toJson();
        String amapJson = toAMap().toJson();
        String baiduJson = toBaiDu().toJson();
        StringBuilder builder = new StringBuilder();
        builder.append("{");
        builder.append("\"").append("gps").append("\"").append(":");
        builder.append(gpsJson).append(",");
        builder.append("\"").append("amap").append("\"").append(":");
        builder.append(amapJson).append(",");
        builder.append("\"").append("baidu").append("\"").append(":");
        builder.append(baiduJson);
        builder.append("}");
        return builder.toString();
    }
}
